/* bheartbeat.c --
 * Copyright 2009  cwxstat LLC., Elkins Park, Pennsylvania.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Authors:
 *     Mike Chirico <mchirico@gmail.com>
 *     
 */


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libaudit.h>
#include <auparse.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include "thread_wrapper.h"
#include "common.h"
#include "engine.h"

#define MAXLOGINS 3
#define MAXLINE 1024

pthread_mutex_t audshow_mutex;
pthread_mutex_t log_mutex;
int AUDS_FLAG = 0;

extern int errno;

struct L_Buf
{
	char *s;
	char *f;
};



int
logprocinfo(char *s, char *file)
{
	int fp, error;

	if ((fp = open(file, O_RDWR | O_CREAT, 0600)) == -1) {
		syslog(LOG_ERR, "Can't open data: %s\n", strerror(errno));
		fprintf(stderr, "Can't open data: %s\n", strerror(errno));
		return -1;
	}

	write(fp, s, strlen(s));

	while (((error = close(fp)) == -1) && (errno == EINTR));
	if (error == -1)
		return -1;

	return 0;

}

static void *
asyc_log(void *arg)
{
	char *s;
	char *f;

	s = ((struct L_Buf *)arg)->s;
	f = ((struct L_Buf *)arg)->f;

	Pthread_mutex_lock(&log_mutex);
	logprocinfo(s, f);
	Pthread_mutex_unlock(&log_mutex);
	free(s);
	free(f);
	free(arg);
	Pthread_detach(pthread_self());
	return (NULL);
}

static void *
Asyc_Log(int tmpFLG, int problems, int sleep, const char *file)
{

	static unsigned long count = 0;
	pid_t pid;
	struct L_Buf *l_b;
	pthread_t t;

	pid = getpid();

	l_b = (struct L_Buf *)malloc(sizeof(struct L_Buf));
	if (l_b == NULL) {
		syslog(LOG_ERR, "l_b is NULL");
		return NULL;
	}
	l_b->s = (char *)malloc(sizeof(char) * 1024);
	if (l_b->s == NULL) {
		syslog(LOG_ERR, "l_b->s is NULL");
		return NULL;
	}
	l_b->f = (char *)malloc(sizeof(char) * 1024);
	if (l_b->f == NULL) {
		syslog(LOG_ERR, "l_b->f is NULL");
		return NULL;
	}
	strncpy(l_b->f, file, 1023);

	snprintf(l_b->s, 1024,
		 "pid=%d tmpFLG=%d problems=%d sleep=%d count=%ld\n", pid,
		 tmpFLG, problems, sleep, ++count);

	Pthread_create(&t, NULL, &asyc_log, l_b);

	return NULL;
}

FILE *
Popen(const char *command, const char *mode)
{
	FILE *fp;

	if ((fp = popen(command, mode)) == NULL)
		fprintf(stderr, "popen error");
	return (fp);
}

int
Pclose(FILE * fp)
{
	int n;

	if ((n = pclose(fp)) == -1)
		fprintf(stderr, "pclose error");
	return (n);
}

char *
Fgets(char *ptr, int n, FILE * stream)
{
	char *rptr;

	if ((rptr = fgets(ptr, n, stream)) == NULL && ferror(stream))
		fprintf(stderr, "fgets error");

	return (rptr);
}

void
Fputs(const char *ptr, FILE * stream)
{
	if (fputs(ptr, stream) == EOF)
		fprintf(stderr, "fputs error");
}

SSL_CTX *
setup_client_ctx(void)
{
	SSL_CTX *ctx;

	ctx = SSL_CTX_new(SSLv23_method());
	if (SSL_CTX_load_verify_locations(ctx, CAFILE, CADIR) != 1)
		int_error("Error loading CA file and/or directory");
	if (SSL_CTX_set_default_verify_paths(ctx) != 1)
		int_error("Error loading default CA file and/or directory");
	if (SSL_CTX_use_certificate_chain_file(ctx, CERTFILE) != 1)
		int_error("Error loading certificate from file");
	if (SSL_CTX_use_PrivateKey_file(ctx, CERTFILE, SSL_FILETYPE_PEM) != 1)
		int_error("Error loading private key from file");
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
	SSL_CTX_set_verify_depth(ctx, 4);
	SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
	if (SSL_CTX_set_cipher_list(ctx, CIPHER_LIST) != 1)
		int_error("Error setting cipher list (no valid ciphers)");
	return ctx;
}


static void
auparse_callback(auparse_state_t * au, auparse_cb_event_t cb_event_type,
		 void *user_data)
{
  //int *event_cnt = (int *)user_data;
  //int record_cnt = 0;

	Pthread_mutex_lock(&audshow_mutex);
	AUDS_FLAG = 1;
	Pthread_mutex_unlock(&audshow_mutex);


}

int
do_client_loop(SSL * ssl)
{
	int err = 0, nread = 0, len = 0;
	char buf[1024];
	buf[0] = '\0';

	auparse_state_t *au;
	int event_cnt = 1;
	au = auparse_init(AUSOURCE_FEED, 0);
	auparse_add_callback(au, auparse_callback, &event_cnt, NULL);

	do {

		/* Maybe this is causing problems? Yeah, I think it is correct now */
		for (nread = 0; nread < (int)sizeof(buf); nread += err) {
			err = SSL_read(ssl, buf + nread, sizeof(buf) - nread);
			/*
			   fprintf(stderr,"nread(%d),err(%d),(sizeof(buf) - (nread+err))=%d\n",
			   nread,err,sizeof(buf) - (nread+err));
			 */

			if (err > 0) {
				buf[nread + err] = '\0';
				//fprintf(stderr,"%s",buf+nread);
				len = strlen(buf + nread);
				auparse_feed(au, buf + nread, len);

			}
			else {
				break;
			}

			if (event_cnt >= 1000) {

				fprintf(stderr,
					"\n***** Clearing buffer *****\n");
				auparse_flush_feed(au);
				auparse_destroy(au);
				event_cnt = 1;
				au = auparse_init(AUSOURCE_FEED, 0);
				auparse_add_callback(au, auparse_callback,
						     &event_cnt, NULL);

			}

		}
	} while (err > 0);

	auparse_flush_feed(au);
	auparse_destroy(au);
	fprintf(stderr, "We get to flush\n");

	return 1;
}

int
main_ssl(int argc, char **argv)
{
	BIO *conn;
	SSL *ssl;
	SSL_CTX *ctx;
	long err;

	init_conf();
	init_OpenSSL();
	seed_prng();

	ctx = setup_client_ctx();

	fprintf(stderr, "SERVER= %s:%s\n", SERVER, PORT);
	conn = BIO_new_connect(SERVER_PORT);
	if (!conn)
		int_error("Error creating connection BIO");

	if (BIO_do_connect(conn) <= 0)
		int_error("Error connecting to remote machine");

	ssl = SSL_new(ctx);
	SSL_set_bio(ssl, conn, conn);
	if (SSL_connect(ssl) <= 0)
		int_error("Error connecting SSL object");
	if ((err = post_connection_check(ssl, SERVER)) != X509_V_OK) {
		fprintf(stderr, "-Error: peer certificate: %s\n",
			X509_verify_cert_error_string(err));
		int_error("Error checking SSL object after connection");
	}
	fprintf(stderr, "SSL Connection opened\n");
	if (do_client_loop(ssl))
		SSL_shutdown(ssl);
	else
		SSL_clear(ssl);
	fprintf(stderr, "SSL Connection closed\n");

	SSL_free(ssl);
	SSL_CTX_free(ctx);
	free_conf();
	return 0;
}

static void *
connect_loop(void *targ)
{
	int argc = 0;
	char **argv = NULL;

	int i = 0, j = 0;

	fprintf(stderr, "clientparse loaded\n");
	/* This for loop is is the connection gets dropped.
	   We're the client, and we need to try and connect to the server */
	for (j = 0; j < 1000; ++j) {
		for (i = 0; i < 200; ++i) {
			main_ssl(argc, argv);
			sleep(2 * i);
			fprintf(stderr, "clientparse trying to connect\n");
		}
	}

	return 0;

}

int
heartbeat_pulse()
{
	int fd = -1;
	int retval = -1;

	if ((fd = audit_open()) < 0) {
		fprintf(stderr, "Cannot open netlink audit socket\n");
		return 1;
	}
	if (audit_log_user_message
	    (fd, AUDIT_USER, "HeartBeat-(bheartbeat)", NULL, NULL, NULL,
	     1) <= 0) {
		retval = -1;
	}
	else
		retval = 1;

	audit_close(fd);
	return retval;

}

int
check_bansshcpp_change()
{
	static struct stat sb;
	struct stat tsb;

	if (sb.st_mtime == 0) {
		if (stat("/var/log/banssh/lock/bansshcpp", &sb) == -1) {
			perror("stat");
			return -1;
		}
		return 0;
	}

	if (stat("/var/log/banssh/lock/bansshcpp", &tsb) == -1) {
		perror("stat");
		return -1;

	}

	if (sb.st_mtime != tsb.st_mtime) {
		sb = tsb;
		return 1;

	}

	else {
		return 0;
	}

}

int
main(int argc, char **argv)
{
	pthread_t t;
	int tmpFLG = 0;
	int problems = 0;
	int bproblems = 0;
	int sleep_time = 25;	// Should read this from config in future.
	int i = 0;
	char buf[MAXLINE];

	FILE *fp = 0;

	daemon(0, 0);
        init_conf();

        sleep_time=atoi(HEARTBEAT_SLEEP);
        syslog(LOG_ERR,"Sleeptime %s (%d) (%d)",HEARTBEAT_SLEEP,atoi(HEARTBEAT_SLEEP),sleep_time);
        syslog(LOG_ERR,"LFILE %s",HEARTBEAT_LFILE);


	if (heartbeat_pulse() <= 0) {
		syslog(LOG_ERR, "Cannot send (Initial) heartbeat_pulse");
	}
	else {
		syslog(LOG_ERR, "banssh HB (0.0.3) pulse confirmed");
	}

	Pthread_create(&t, NULL, &connect_loop, NULL);

	while (1) {

		Pthread_mutex_lock(&audshow_mutex);
		AUDS_FLAG = 0;
		Pthread_mutex_unlock(&audshow_mutex);
		check_bansshcpp_change();

		// Wait N seconds. But keep logging
		for (i = 0; i < sleep_time; ++i) {
			Asyc_Log(tmpFLG, problems, sleep_time,
				 HEARTBEAT_LFILE);

			sleep(1);
		}

		Pthread_mutex_lock(&audshow_mutex);
		tmpFLG = AUDS_FLAG;
		Pthread_mutex_unlock(&audshow_mutex);

		/* problems 0=okay, 1,2,3,4 increasing severity */
		Asyc_Log(tmpFLG, problems, sleep_time,
			 HEARTBEAT_LFILE);

		if (tmpFLG == 0) {
			// This forces a pulse
			if (heartbeat_pulse() <= 0) {
				syslog(LOG_ERR,
				       "Cannot send heartbeat_pulse");
			}
			Pthread_mutex_lock(&audshow_mutex);
			tmpFLG = AUDS_FLAG;
			Pthread_mutex_unlock(&audshow_mutex);

			if (tmpFLG == 0) {
				fprintf(stderr,
					"Getting delay, or we have a problem and need to act\n");
				problems++;
				if (problems > 4) {
					syslog(LOG_ERR,
					       "HEARTBEAT problems=%d (restarting)",
					       problems);
					fp = Popen
						("/etc/init.d/auditd restart",
						 "r");
					while (Fgets(buf, MAXLINE, fp) !=
					       NULL) {
						syslog(LOG_ERR, "%s", buf);
					}
					Pclose(fp);
					fp = Popen
						("/etc/init.d/bansshd restart",
						 "r");
					while (Fgets(buf, MAXLINE, fp) !=
					       NULL) {
						syslog(LOG_ERR, "%s", buf);
					}
					Pclose(fp);
					fp = Popen
						("/etc/init.d/bheartbeat restart",
						 "r");
					while (Fgets(buf, MAXLINE, fp) !=
					       NULL) {
						syslog(LOG_ERR, "%s", buf);
					}
					Pclose(fp);

				}
			}

		}
		else {

			problems = 0;

		}

		if (check_bansshcpp_change() == 1)
			bproblems = 0;
		else
			bproblems++;

		if (bproblems > 4) {
			syslog(LOG_ERR,
			       "bansshd is not responding. Restarting");
			fp = Popen("/etc/init.d/bansshd restart", "r");
			while (Fgets(buf, MAXLINE, fp) != NULL) {
				syslog(LOG_ERR, "%s", buf);
			}
			Pclose(fp);
			bproblems = 0;

		}

		/*
		   If you get audit input, you're ok.

		   If you don't get audit input, we could try calling
		   a python program. Why? (It's easier to do system admin changes)
		   a. check netstat
		   b. may need to restart this program. Or may need to reconnect.

		   I'm not sure if the python idea makes sense. Seem like an extra
		   layer of confusion.

		 */

	}
        free_conf();
	return 0;
}
